package com.igeek.boot.service;

import com.igeek.boot.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * @Author fengqq
 * @Description TODO
 * @Date 2024/9/6  14:37
 */
@Service
@Slf4j
public class SeckillService {

    private static final String stockKey = "kill_sk::"; //stockKey  string字符串
    private static final String userKey = "kill_uk::";//userKey  set无序不重复集合

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //基础版 秒杀
    //Jmeter性能测试 线程数：100 循环次数：10  请求总数量：1000 结果：-7 库存负数 超卖
    public Result kill(Integer pid,String uid) {

        //不允许秒杀的情况：
        //1.活动还没开始    kill_sk::pid 不存在的
        Object stock = redisTemplate.opsForValue().get(stockKey + pid);
        if(ObjectUtils.isEmpty(stock)){
            return new Result(500,"秒杀失败，活动还没开始~");
        }
        //2.库存<=0
        int stockNum = Integer.parseInt(stock+"");
        if(stockNum<=0){// 1 线程1 线程2
            return new Result(500,"秒杀失败，活动已结束~");
        }
        //3.当前用户已经进入到秒杀成功的用户列表中
        Boolean flag = redisTemplate.opsForSet().isMember(userKey + pid, uid);
        if(flag){
            return new Result(500,"秒杀失败，当前用户已参加秒杀活动~");
        }

        //允许秒杀的情况：
        //1.库存 -1
        redisTemplate.opsForValue().decrement(stockKey + pid);
        //2.将当前用户uid存储到 秒杀成功的用户列表中
        redisTemplate.opsForSet().add(userKey + pid,uid);
        return new Result(200,"秒杀成功~");
    }


    //事务+乐观锁 秒杀
    //Jmeter性能测试 库存数10  线程数：100 循环次数：10  请求总数量：1000 结果：0 超卖解决了
    //Jmeter性能测试 库存数600  线程数：300 循环次数：15  请求总数量：3000 结果：323 库存遗留了？？
    public Result killByTrans(Integer pid,String uid) {

        //使用SessionCallback接口 从而可以保证所有的命令都是使用同一个redis连接来处理的
        Result result = redisTemplate.execute(new SessionCallback<Result>() {
            @Override
            public Result execute(RedisOperations operations) throws DataAccessException {
                //开启乐观锁 监视库存
                operations.watch(stockKey + pid);

                //不允许秒杀的情况：
                //1.活动还没开始    kill_sk::pid 不存在的
                Object stock = operations.opsForValue().get(stockKey + pid);
                log.info("stock:{}",stock);
                if(ObjectUtils.isEmpty(stock)){
                    return new Result(500,"秒杀失败，活动还没开始~");
                }
                //2.库存<=0
                int stockNum = Integer.parseInt(stock+"");
                if(stockNum<=0){// 1 线程1 线程2
                    return new Result(500,"秒杀失败，活动已结束~");
                }
                //3.当前用户已经进入到秒杀成功的用户列表中
                Boolean flag = operations.opsForSet().isMember(userKey + pid, uid);
                if(flag){
                    return new Result(500,"秒杀失败，当前用户已参加秒杀活动~");
                }

                //允许秒杀的情况：开启事务 解决超卖问题
                operations.multi();
                //1.库存 -1
                operations.opsForValue().decrement(stockKey + pid);
                //2.将当前用户uid存储到 秒杀成功的用户列表中
                operations.opsForSet().add(userKey + pid,uid);
                //执行事务 返回值就是执行的命令集
                List execlist = operations.exec();
                if(CollectionUtils.isEmpty(execlist)){
                    return new Result(500,"秒杀失败，别的线程已经抢先更新过库存则放弃库存更新任务");
                }
                return new Result(200,"秒杀成功~");
            }
        });
        return result;
    }

    //lua脚本 秒杀
    //Jmeter性能测试 库存数600  线程数：300 循环次数：15  请求总数量：3000 结果：0
    public Result killByLua(Integer pid,String uid) {
        //执行lua脚本
        //return new Result(500,"秒杀失败，活动还没开始~"); 1
        //return new Result(500,"秒杀失败，活动已结束~"); 2
        //return new Result(500,"秒杀失败，当前用户已参加秒杀活动~"); 3
        //return new Result(200,"秒杀成功~"); 4

        //加载lua脚本
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("kill.lua")));
        //执行脚本 public <T> T execute(RedisCallback<T> action)
        Object result = redisTemplate.execute((RedisCallback<Object>) connection-> {
            //eval(byte[] script, ReturnType returnType, int numKeys, byte[]... keysAndArgs);
            return connection.eval(
                    script.getScriptAsString().getBytes(), //脚本字节数组
                    ReturnType.INTEGER, //脚本返回值类型
                    2,                  //传入的key的数量
                    uid.toString().getBytes(), //uid的字节数组
                    pid.toString().getBytes() //pid的字节数组
            );
        });
        //根据lua脚本返回值 封装result对象
        Integer value = Integer.valueOf(result.toString());
        if(value == 1){
            return new Result(500,"秒杀失败，活动还没开始~");
        }else if(value==2){
            return new Result(500,"秒杀失败，活动已结束~");
        }else if(value==3){
            return new Result(500,"秒杀失败，当前用户已参加秒杀活动~");
        }else if(value==4){
            return new Result(200,"秒杀成功~");
        }

        return  new Result(500,"秒杀失败~");
    }
}
